﻿using System;
using System.Linq;

using zijian666.Converts.Abstractions;
using zijian666.Converts.Core;
using zijian666.Converts.Extensions;

namespace zijian666.Converts.Convertor.Base;

/// <summary>
/// 转换器基类
/// </summary>
public abstract class BaseConvertor<T> : IConvertor<T>
{
    /// <summary>
    /// 构造函数
    /// </summary>
    protected BaseConvertor()
    {
        OutputType = typeof(T);
        TypeName = OutputType.FullName;
        TypeFriendlyName = OutputType.GetFriendlyName();
    }

    /// <summary>
    /// 输出类型
    /// </summary>
    public virtual Type OutputType { get; }

    /// <summary>
    /// <seealso cref="OutputType"/>.FullName
    /// </summary>
    public virtual string TypeName { get; }

    /// <summary>
    /// <seealso cref="OutputType"/>的友好名称
    /// </summary>
    public virtual string TypeFriendlyName { get; }

    /// <inheritdoc />
    public virtual uint Priority { get; } = 1;

    /// <inheritdoc />
    /// <remarks>代理 <seealso cref="IConvertor{T}.Convert(IConvertContext, object)"/> </remarks>
    ConvertResult<T> IConvertor<T>.Convert(IConvertContext context!!, object? input)
    {
        ExceptionCollector? ecollector = null;

        // 如果是代理，则获取实际对象
        if (input is IObjectProxy proxy)
        {
            input = proxy.GetActualObject();
        }

        // 无需转型
        if (input is T t)
        {
            return new ConvertResult<T>(t);
        }

        var type = input?.GetType();
        // 1. 匹配强制转换和隐式转换
        if (input is not null)
        {
            var cast = ConvertOperatorHelper.Get<T>(type!);
            if (cast is not null)
            {
                var result = new ConvertResult<T>(cast(input));
                if (result.Value is not null && OutputType.IsEnum && context.Settings.StrictEnum)
                {
                    return StrictEnum(context, result.Value);
                }
                return result;
            }
        }

        IConvertible? convertible0 = null;
        // 2. 如果是 IConvertible 获取实际对象
        if (input is IConvertible convertible)
        {
            convertible0 = convertible;
            try
            {
                input = convertible.GetTypeCode() switch
                {
                    TypeCode.Boolean when input is not bool => convertible.ToBoolean(context.Settings.CultureInfo),
                    TypeCode.Byte when input is not byte => convertible.ToByte(context.Settings.CultureInfo),
                    TypeCode.Char when input is not char => convertible.ToChar(context.Settings.CultureInfo),
                    TypeCode.DateTime when input is not DateTime => convertible.ToDateTime(context.Settings.CultureInfo),
                    TypeCode.Decimal when input is not decimal => convertible.ToDecimal(context.Settings.CultureInfo),
                    TypeCode.Double when input is not double => convertible.ToDouble(context.Settings.CultureInfo),
                    TypeCode.Int16 when input is not short => convertible.ToInt16(context.Settings.CultureInfo),
                    TypeCode.Int32 when input is not int => convertible.ToInt32(context.Settings.CultureInfo),
                    TypeCode.Int64 when input is not long => convertible.ToInt64(context.Settings.CultureInfo),
                    TypeCode.SByte when input is not sbyte => convertible.ToSByte(context.Settings.CultureInfo),
                    TypeCode.Single when input is not float => convertible.ToSingle(context.Settings.CultureInfo),
                    TypeCode.UInt16 when input is not ushort => convertible.ToUInt16(context.Settings.CultureInfo),
                    TypeCode.UInt32 when input is not uint => convertible.ToUInt32(context.Settings.CultureInfo),
                    TypeCode.UInt64 when input is not ulong => convertible.ToUInt64(context.Settings.CultureInfo),
                    TypeCode.DBNull when input is not DBNull => DBNull.Value,
                    TypeCode.String when input is not string => convertible.ToString(context.Settings.CultureInfo),
                    TypeCode.Object => convertible.ToType(typeof(object), context.Settings.CultureInfo),
                    TypeCode.Empty => null,
                    _ => input,
                };

            }
            catch (Exception ex)
            {
                ExceptionCollector.Collect(ref ecollector, ex);
            }
        }

        // 4. 调用子类实现转换方法, 如果失败再尝试其他方案
        {
            var result = Convert(context, input);
            if (result.Success)
            {
                if (result.Value is not null && OutputType.IsEnum && context.Settings.StrictEnum)
                {
                    return StrictEnum(context, result.Value);
                }
                return result;
            }

            // 4.1 如果 input = null  则直接转换, 不用进行其他尝试
            if (input is null or DBNull)
            {
                return result;
            }

            // 收集异常
            if (result.Exception is not null)
            {
                ExceptionCollector.Collect(ref ecollector, result.Exception);
            }
        }

        // 5. 如果是 IConvertible 且之前的转换都失败了,则重新尝试 转为IFrom<IConvertible, T>之后转换 
        if (convertible0 is not null && ReferenceEquals(convertible0, input) && this is IFrom<IConvertible, T> conv)
        {
            var result = conv.TryFrom(context, convertible0, ref ecollector);
            if (result.Success)
            {
                if (result.Value is not null && OutputType.IsEnum && context.Settings.StrictEnum)
                {
                    // 处理严格模式严格模式下的枚举值, 为了减少值类型复制的额外开销, 就不封装了, 下面会有部分重复的代码
                    return StrictEnum(context, result.Value);
                }
                return result;
            }
        }

        // 6. 如果有翻译器则尝试翻译 input
        var values = context.Settings.Translators?.Where(x => x.CanTranslate(type!)).Select(x => x.Translate(context, input));
        foreach (var value in values ?? Array.Empty<object>())
        {
            // 翻译后的值等于原值则忽略
            if (ReferenceEquals(value, input))
            {
                continue;
            }
            // 尝试转换翻译后的值
            var result = Convert(context, value);
            if (result.Success)
            {
                if (result.Value is not null && OutputType.IsEnum && context.Settings.StrictEnum)
                {
                    return StrictEnum(context, result.Value);
                }
                return result;
            }
            if (result.Exception is not null)
            {
                ExceptionCollector.Collect(ref ecollector, result.Exception);
            }
        }

        // end. 如果到这里都没有转换成功, 则根据收集到的异常 生成异常消息
        var message = ExceptionHelper.ConvertFailMessage(input, TypeFriendlyName, context.Settings.CultureInfo);
        if (ecollector is null)
        {
            return new InvalidCastException(message);
        }
        return ecollector.ToException(message);
    }

    /// <summary>
    /// 返回指定类型的对象，其值等效于指定对象。
    /// </summary>
    /// <param name="context"> 上下文 </param>
    /// <param name="input"> 需要转换类型的对象 </param>
    public abstract ConvertResult<T> Convert(IConvertContext context, object? input);

    /// <summary>
    /// 严格模式的枚举值
    /// </summary>
    /// <param name="context">转换上下文</param>
    /// <param name="value">枚举值</param>
    /// <returns></returns>
    private ConvertResult<T> StrictEnum(IConvertContext context, T value)
    {
        // 枚举值是default,或已定义 则直接返回
        if (value!.Equals(default(T)) || Enum.IsDefined(OutputType, value))
        {
            return value;
        }

        // 如果枚举是 Flags
        if (Attribute.IsDefined(OutputType, typeof(FlagsAttribute)))
        {
            // 则判断ToString是否有逗号, (AssemblyFlags.PublicKey | (AssemblyFlags)2).ToString() 会得到 "3" 
            if (value.ToString().IndexOf(',') >= 0)
            {
                return value;
            }
        }

        var ex = new InvalidCastException("严格枚举模式下枚举值不能超过枚举定义范围");
        return context.ConvertFail(this, value, ex);
    }
}
